'use strict'

const titbit = require('titbit');
const fs = require('fs');
const dbcfg = require('./dbconfig');
const pg = require('pg');
const { request } = require('https');
const wxkey = require('./wxkey');
const {httpcli} = require('gohttp');
const token = require('titbit-token');
// 对于一个程序来说，知道其运行位置并在其运行位置上扩展是非常重要的
// 如果改变运行路径会导致images文件夹创建在错误的位置，所以需要在开始时切换工作目录
process.chdir(__dirname);

try {
    fs.accessSync('./images');
} catch (err) {
    fs.mkdirSync('./images');
}


let psql = new pg.Pool(dbcfg);


let cert_path = '/usr/local/share';
const app = new titbit({
    // 开启调试模式，若有错误会输出错误信息
    debug : true,
    globalLog:true,
    cert: `${cert_path}/api.jinjin.work.pem`,
    key:`${cert_path}/api.jinjin.work.key`
})
// 添加路由
app.get("/",async c => {//c就是请求上下文
    c.send('OK!')
})
/**-------------------content路由------------------------------------- */
// 不能添加相同的路由，相同的路由会报错
app.get("/content",async c => {
    let ret = await psql.query('select * from content');
    // 等效于c.res.body = ret.rows
    c.send(ret.rows)//查询到的结果在rows属性里
    //此处不能end，因为我们是长久保持的服务
})
// 获取具体内容
app.get("/content/:id",async c => {
    let sqltext = `SELECT * FROM content WHERE id=$1`
    let r = await psql.query(sqltext,[c.param.id])
    if (r.rowCount === 0) {
        return c.status(404).send('content not found')
    }
    c.send(r.rows[0])
})
// 创建内容
app.post("/content",async c => {
    try {
        let data = JSON.parse(c.body);

        let sqltext = `INSERT INTO `
                    +`content(id,add_time,detail,title,update_time)`
                    +`VALUES ($1,$2,$3,$4,$5)`;
        //生成唯一ID
        let id = c.helper.makeId();
        let tm = Date.now();

        let r = await psql.query(sqltext,[id,tm,data.detail,data.title,tm]);
        if (r.rowCount == 0) {
            return c.status(500).send("failed");
        }
        c.send(id)
    } catch (err) {
        c.status(400).send('It is not json data')
    }
})
app.delete('/content/:id', async c => {
    try {
        let sqltext = `DELETE FROM content WHERE id=$1`;
        let r = await psql.query(sqltext,[c.param.id]);
        if (r.rowCount == 0) {
            return c.status(500).send('Fail to delete!');
        }
    } catch (error) {
        c.status(400).send("Failed!");
    }
})


/**-------------------content路由 -END------------------- */



app.get('/upload',async c => {
    // c.reply是响应对象，可写的流，
    // 在HTTP/1.1中c.reply指向response；在HTTP/2中c.reply指向http2stream
    await c.helper.pipe('./httphandleprogram/upload.html',c.reply)//请求模块里有一个helper模块，提供很多工具，比如生成唯一id等等
    
    
})
app.get('/randimg', async c => {//随机返回一张图片
    fs.readdir('./images' ,(e,files) =>{
        if (e) {
            c.send(e)
        }
        else {
            let i = parseInt(Math.random()*files.length);//应返回一个图片的数据
            c.send(files[i]);
        }
    })
})
app.post('/upload',async c => {
    // 根据name获取文件对象
    // getFile可以获取到文件的属性并以对象的形式保存：{filename,'content-type',start,end,lenght}
    let f = c.getFile('image');
    
    if (!f) {
        return c.status(400).send('image not found')
    }

    // 根据传入的原始文件名生成唯一文件名，主要使用f.filename的扩展名 
    let fname = c.helper.makeName(f.filename);

    // 移动文件到指定位置
    await c.moveFile(f,`./images/${fname}`);//如果没有images目录的话会导致报错

    c.send(fname);
})
// 返回图片数据
// ha
// 此处的:name可以替换为其他的具体图片名字，只是代表后面提交的必须提交具体的参数，:是识别参数的符号，这是在框架层面上实现的
app.get('/images/:name',async c =>{
    // c.param是一个对象，其中的key值是路由参数的名称
    let imgname = c.param.name;

    await c.helper.pipe(`./images/${imgname}`,c.reply)


})

let tok = new token({
    // token有效期，单位为秒
    expires:45,
    //必须是32位字符
    // key就是加解密的秘钥，相当于密码
    key:'abcdefghijklmnopqrstuvwxyzkutaha',

    // 必须是16位字符
    // iv就是作为初始干扰的数据，底层需要用它进行与或操作
    iv:'qwertyuiopasdfgh'
})

app.get('/mp-login/:code', async c => {
    let auth_url = 'https://api.weixin.qq.com/sns/jscode2session'
                +`?appid=${wxkey.appid}`
                +`&secret=${wxkey.secret}`
                +`&js_code=${c.param.code}`
                +`&grant_type=authorization_code`;
    let ret = await httpcli.get(auth_url)
                            .then(res => {
                                return res.json();
                            })
    c.send(tok.make(ret));
    
})

app.get('/decrypt/:token',async c=> {
    c.send(tok.verify(c.param.token));
})

// *在这个框架中表示任意字符
// app.get('/static/*',callback)

// 运行在6001端口
app.run(6001);

/**
 * HTTP消息头content-type指定了数据的类型（数据的格式）。
 * 
 * Client                                                           Server
 *    content-type描述的值要和提交的Body数据格式一致。                   根据content-type的值决定如何解析Body数据。
 * 
 * content-type:
 *      multipart/form-data;boundary=xxx  -----   文件解析
 *      text/*    -----------------------------   文本类型
 *      application/*   -----------------------   要看具体值，application/json也是文本类型
 *      ......   ------------------------------   每种类型如何处理是双方设计好的通信方式
 * 
 * 到Web连框架这一层面，则如何处理是开发框架设计好的，但通常都要遵循一个原则：
 *         允许开发者做自己的处理。
 * 
 * 在本框架范围内：
 *      multipart类型  c.body是object类型，文件信息解析到c.files。
 *      application/x-www-form-urlencoded  传统的表单类，c.body是object类型，用于获取提交的表单数据。
 *      text/*  c.body是文本类型，c.body = c.rawBody.toString('utf-8')
 *      其他类型：  c.body是buffer类型，就是对c.rawBody的引用。
 */